package deepr.lib;


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import deepr.anno.Type;
import deepr.core.PropertyData;
import deepr.core.SchemaData;
import deepr.core.SchemaManager;
import deepr.entity.DeeprListResponse;
import deepr.exception.DeeprAssert;
import deepr.exception.DeeprException;
import javassist.bytecode.ByteArray;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.Record;
import org.neo4j.driver.Value;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.value.ListValue;
import org.neo4j.driver.internal.value.MapValue;
import org.omg.CORBA.IDLTypeOperations;
import org.springframework.cglib.core.Local;

import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
public final class Map2Bean {

  public static DateFormat dateFormat = new SimpleDateFormat("yyyy-dd-MM HH:mm:ss");
  private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

  public static <T> List<T> mapToBean(Record record, Class<T> czz) throws IllegalAccessException, InstantiationException {
    ListValue list = (ListValue) record.get("list");
    log.debug("list: {}", list);
    SchemaData schemaData = SchemaManager.getSchemaData(czz);
    T o = czz.newInstance();
    return list.asList(row -> {
      try {
        return mapToBean((MapValue) row, czz, schemaData);
      } catch (Exception e) {
        log.debug("mapToBean Exception, row: {}, stack: {}", e.getMessage(), e.getStackTrace()[0]);
        return o;
      }
    });
  }

  public static <T> List<T> mapToBean(Record record, String obj) throws IllegalAccessException, InstantiationException {
    Class<T> czz = SchemaManager.getSchemaData(obj).getSchema();
    ListValue list = (ListValue) record.get("list");
    log.debug("list: {}", list);
    SchemaData schemaData = SchemaManager.getSchemaData(czz);
    T o = czz.newInstance();
    return list.asList(row -> {
      try {
        return mapToBean((MapValue) row, czz, schemaData);
      } catch (Exception e) {
        log.debug("mapToBean Exception, row: {}, stack: {}", e.getMessage(), e.getStackTrace()[0]);
        return o;
      }
    });
  }


  public static <T> List<T> mapToBean(ListValue list, Class<T> beanClass, SchemaData schemaData) {
    return list.asList(row -> {
      try {
        return mapToBean((MapValue) row, beanClass, schemaData);
      } catch (Exception e) {
        log.debug("mapToBean Exception, row: {}, message: {}, stack: {}", row, e.getMessage(), e.getStackTrace()[0]);
        return null;
      }
    });
  }

  public static <T> T mapToBean(MapValue map, Class<T> beanClass, SchemaData schemaData) throws Exception {
//    log.debug("map: {}, czz: {}, schema: {}", map, beanClass, schemaData.getName());
    if (map.isNull() || map.isEmpty())
      return null;
    T object = beanClass.newInstance();
    Field[] fields = object.getClass().getDeclaredFields();
    for (Field field : fields) {
      int mod = field.getModifiers();
      if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
        continue;
      }
      String filedName = field.getName();
      PropertyData propertyData = schemaData.getPropertyData(filedName);
      if (propertyData == null)
        continue;
      field.setAccessible(true);
      String dbName = propertyData.getName();
      Value value = map.get(dbName);
      if (value.isNull())
        continue;
      if (map.containsKey(dbName)) {
        Object v = null;
        boolean isMapValue = value instanceof MapValue;
        boolean isListValue = value instanceof ListValue;
        if (propertyData.isSchema() && (isMapValue || isListValue)) {
          if (propertyData.isRelationProperty()) {
            SchemaData relationSchema = schemaData.getRelationSchemaData(propertyData.getName());
            Class<?> relationCzz = propertyData.getSchema();
            if (isMapValue) {
              v = mapToBean((MapValue) value, relationCzz, relationSchema);
            } else {
              v = mapToBean((ListValue) value, relationCzz, relationSchema);
            }
            PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
            Method method = descriptor.getWriteMethod();
            method.invoke(object, v);
            continue;
          }
          Class<?> propertyBean = propertyData.getSchema();
          SchemaData propertySchema = SchemaManager.getSchemaData(propertyBean);
          if (isMapValue) {
            v = mapToBean((MapValue) value, propertyBean, propertySchema);
          }
          if (isListValue) {
            v = mapToBean((ListValue) value, propertyBean, propertySchema);
          }
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, v);
          continue;
        }
        if (field.getType() == JSONArray.class && value.type() == InternalTypeSystem.TYPE_SYSTEM.STRING()) {
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, JSONArray.parseArray(value.asString()));
          continue;
        }
        if (field.getType() == JSONObject.class && value.type() == InternalTypeSystem.TYPE_SYSTEM.STRING()) {
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, JSONObject.parse(value.asString()));
          continue;
        }
        v = convertType(field, filedName, value, propertyData.getType());
        if (v == null)
          continue;
        if (field.getType().isPrimitive()) {
          field.set(object, v);
          continue;
        }
        PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
        Method method = descriptor.getWriteMethod();
        method.invoke(object, v);
      }
    }
    return object;
  }


  public static Object convertType(Field field, String filedName, Value value, Type type) throws DeeprException, ParseException {
    log.debug("name:{}, value: {}, valueType:{}, type: {}", filedName, value, value.getClass(), field.getType());
    if (value.isNull())
      return null;
    if (value instanceof ListValue) {
      DeeprAssert.isTrue(field.getType().isArray(), filedName + " must be array");
      Class<?> fieldComponentType = field.getType().getComponentType();
      Object o = Array.newInstance(fieldComponentType, value.size());
      int i = 0;
      value.asList().forEach(ele -> {
        Array.set(o, i, ele);
      });
      return o;
    }

    if (String.class.equals(field.getType())) {
      return value.asString();
    }
    if (Long.class.equals(field.getType()) || long.class.equals(field.getType())) {
      return value.asLong();
    }
    if (Float.class.equals(field.getType()) || float.class.equals(field.getType())) {
      return value.asFloat();
    }
    if (Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType())) {
      return value.asBoolean();
    }
    if (ByteArray.class.equals(field.getType())) {
      return value.asByteArray();
    }
    if (Double.class.equals(field.getType()) || double.class.equals(field.getType())) {
      return value.asDouble();
    }
    if (Integer.class.equals(field.getType()) || int.class.equals(field.getType())) {
      return value.asInt();
    }
    if (LocalDateTime.class.equals(field.getType())) {
      return LocalDateTime.parse(value.asString(), dateTimeFormatter);
    }
    if (LocalDate.class.equals(field.getType())) {
      return LocalDate.parse(value.asString());
    }
    if (LocalTime.class.equals(field.getType())) {
      return LocalTime.parse(value.asString());
    }
    if (Date.class.equals(field.getType())) {
      return dateFormat.parse(value.asString());
    }
    if (Duration.class.equals(field.getType())) {
      return Duration.parse(value.asString());
    }

    if (InternalTypeSystem.TYPE_SYSTEM.TIME().equals(value.type())) {
      return value.asLocalTime();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.DATE_TIME().equals(value.type())) {
      return value.asLocalDateTime();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.DURATION().equals(value.type())) {
      return value.asIsoDuration();
    }
    return null;
  }


  public static <T> T mapToBean(MapValue map, Class<T> beanClass) throws Exception {
    T object = beanClass.newInstance();
    SchemaData schemaData = SchemaManager.getSchemaData(beanClass);
    Field[] fields = object.getClass().getDeclaredFields();
    for (Field field : fields) {
      int mod = field.getModifiers();
      if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
        continue;
      }
      String filedName = field.getName();
      PropertyData propertyData = schemaData.getPropertyData(filedName);
      if (propertyData == null)
        continue;
      field.setAccessible(true);
      String dbName = propertyData.getName();
      Value value = map.get(dbName);
      if (map.containsKey(dbName)) {
        if (field.getType() == JSONArray.class && value.type() == InternalTypeSystem.TYPE_SYSTEM.STRING()) {
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, JSONArray.class);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, JSONArray.parseArray(value.asString()));
          continue;
        }
        if (field.getType() == JSONObject.class && value.type() == InternalTypeSystem.TYPE_SYSTEM.STRING()) {
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, JSONObject.class);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, JSONObject.parse(value.asString()));
          continue;
        }
        if (propertyData.isSchema() && value instanceof MapValue) {
          Class<?> propertyBean = propertyData.getSchema();
          value = (Value) mapToBean((MapValue) value, propertyBean);
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, propertyBean);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, value);
          continue;
        }
        Object v = convertType(filedName, value, propertyData.getType());
        field.set(object, v);
      }
    }
    return object;
  }

  public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) throws Exception {
    T object = beanClass.newInstance();
    SchemaData schemaData = SchemaManager.getSchemaData(beanClass);
    Field[] fields = object.getClass().getDeclaredFields();
    for (Field field : fields) {
      int mod = field.getModifiers();
      if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
        continue;
      }
      String filedName = field.getName();
      PropertyData propertyData = schemaData.getPropertyData(filedName);
      if (propertyData == null) {
        continue;
      }
      field.setAccessible(true);
      String dbName = propertyData.getName();
      Object value = map.get(propertyData.getName());
      if (map.containsKey(dbName)) {
        if (field.getType() == JSONArray.class && value instanceof String) {
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, JSONArray.parseArray((String) value));
          continue;
        }
        if (field.getType() == JSONObject.class && value instanceof String) {
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, JSONObject.parse((String) value));
          continue;
        }
        if (propertyData.isSchema() && value instanceof Map) {
          Class<?> propertyBean = propertyData.getSchema();
          value = mapToBean((Map) value, propertyBean);
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, propertyBean);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, value);
          continue;
        }
        Class<?> type = field.getType();
        if (type.isPrimitive() || isBasicDataTypes(type)) {
          field.set(object, value);
          continue;
        }
        if (type == LocalDate.class && value != null) {
          LocalDate v = ((java.sql.Date) value).toLocalDate();
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, v);
          continue;
        }
        if (type == java.sql.Date.class && value != null) {
          java.util.Date v = new java.util.Date(((java.sql.Date) value).getTime());
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, v);
          continue;
        }
        if (type == Timestamp.class && value != null) {
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, (Timestamp) value);
          continue;
        }
        if (type == LocalDateTime.class && value != null) {
          LocalDateTime v = ((java.sql.Timestamp) value).toLocalDateTime();
          PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
          Method method = descriptor.getWriteMethod();
          method.invoke(object, v);
          continue;
        }
        PropertyDescriptor descriptor = new PropertyDescriptor(filedName, beanClass);
        Method method = descriptor.getWriteMethod();
        method.invoke(object, JSONObject.parse((String) value), beanClass);
      }
    }
    return object;
  }

  public static boolean isBasicDataTypes(Class<?> clazz) {
    Set<Class<?>> classSet = new HashSet<>();
    classSet.add(Integer.class);
    classSet.add(Long.class);
    classSet.add(Short.class);
    classSet.add(Float.class);
    classSet.add(Double.class);
    classSet.add(Boolean.class);
    classSet.add(Byte.class);
    classSet.add(Character.class);
    classSet.add(String.class);
    return classSet.contains(clazz);
  }


  /**
   * 对象转Map
   *
   * @param object
   * @return
   * @throws IllegalAccessException
   */
  public static Map<String, Object> beanToMap(Object object) throws IllegalAccessException {
    Map<String, Object> map = new HashMap<>();
    Field[] fields = object.getClass().getDeclaredFields();
    for (Field field : fields) {
      field.setAccessible(true);
      map.put(field.getName(), field.get(object));
    }
    return map;
  }

  public static String captureName(String name) {
    char[] cs = name.toCharArray();
    cs[0] -= 32;
    return String.valueOf(cs);
  }

  public static String firstLowerName(String name) {
    char[] cs = name.toCharArray();
    cs[0] += 32;
    return String.valueOf(cs);
  }

  private static Pattern linePattern = Pattern.compile("_(\\w)");

  /**
   * 下划线转驼峰
   */
  public static String lineToHump(String str) {
    str = str.toLowerCase();
    Matcher matcher = linePattern.matcher(str);
    StringBuffer sb = new StringBuffer();
    while (matcher.find()) {
      matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
    }
    matcher.appendTail(sb);
    return sb.toString();
  }

  private static Pattern humpPattern = Pattern.compile("[A-Z]");

  /**
   * 驼峰转下划线,效率比上面高
   */
  public static String humpToLine(String str) {
    Matcher matcher = humpPattern.matcher(str);
    StringBuffer sb = new StringBuffer();
    while (matcher.find()) {
      matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
    }
    matcher.appendTail(sb);
    return sb.toString();
  }


  public static Object convertType(String filedName, Value value, Type type) {
//    log.debug("name:{}, value: {}, valueType:{}, type: {}", filedName, value, value.getClass(), type);
    if (InternalTypeSystem.TYPE_SYSTEM.FLOAT().equals(value.type())) {
      return value.asFloat();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.BYTES().equals(value.type())) {
      return value.asByteArray();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.BOOLEAN().equals(value.type())) {
      return value.asBoolean();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.NUMBER().equals(value.type())) {
      switch (type) {
        case LONG:
          return value.asLong();
        case FLOAT:
          return value.asFloat();
        case DOUBLE:
          return value.asDouble();
        default:
          return value.asInt();
      }
    }
    if (InternalTypeSystem.TYPE_SYSTEM.INTEGER().equals(value.type())) {
      if (type == Type.LONG)
        return value.asLong();
      return value.asInt();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.LIST().equals(value.type())) {
      return value.asList();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.DATE().equals(value.type())) {
      return value.asLocalDate();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.TIME().equals(value.type())) {
      return value.asLocalTime();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.DATE_TIME().equals(value.type())) {
      return value.asLocalDateTime();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.DURATION().equals(value.type())) {
      return value.asIsoDuration();
    }
    if (InternalTypeSystem.TYPE_SYSTEM.NULL().equals(value.type())) {
      return null;
    }
    return value.asString();
  }


}